package h2quic

import (
	"net/http"
)

type myHandler struct{}

func (h *myHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	w.Write([]byte("Hello, world!"))
}

// // // type Handler interface {
// // // 	ServeHTTP(ResponseWriter, *Request)
// // // }

// // // FileHandler is a type to extend http.fileHandler to support write the data to stream at once
// // type FileHandler struct {
// // 	fh http.FileHandler
// // }

// // // type FileSystem struct {
// // // 	fs http.FileSystem
// // // }

// // func FileServer(root FileSystem) Handler {
// // 	return &fileHandler{root}
// // }

// // Copyright 2009 The Go Authors. All rights reserved.
// // Use of this source code is governed by a BSD-style
// // license that can be found in the LICENSE file.

// // HTTP file system request handler

// import (
// 	"errors"
// 	"fmt"
// 	"io"
// 	"mime"
// 	"mime/multipart"
// 	"net/http"
// 	"net/textproto"
// 	"path/filepath"
// 	"strconv"
// 	"time"
// )

// var errSeeker = errors.New("seeker can't seek")
// var unixEpochTime = time.Unix(0, 0)

// // isZeroTime reports whether t is obviously unspecified (either zero or Unix()=0).
// func isZeroTime(t time.Time) bool {
// 	return t.IsZero() || t.Equal(unixEpochTime)
// }

// func setLastModified(w http.ResponseWriter, modtime time.Time) {
// 	if !isZeroTime(modtime) {
// 		w.Header().Set("Last-Modified", modtime.UTC().Format(TimeFormat))
// 	}
// }
// func setLastModified(w http.ResponseWriter, modtime time.Time) {
// 	if !isZeroTime(modtime) {
// 		w.Header().Set("Last-Modified", modtime.UTC().Format(TimeFormat))
// 	}
// }
// func checkIfMatch(w http.ResponseWriter, r *Request) condResult {
// 	im := r.Header.Get("If-Match")
// 	if im == "" {
// 		return condNone
// 	}
// 	for {
// 		im = textproto.TrimString(im)
// 		if len(im) == 0 {
// 			break
// 		}
// 		if im[0] == ',' {
// 			im = im[1:]
// 			continue
// 		}
// 		if im[0] == '*' {
// 			return condTrue
// 		}
// 		etag, remain := scanETag(im)
// 		if etag == "" {
// 			break
// 		}
// 		if etagStrongMatch(etag, w.Header().get("Etag")) {
// 			return condTrue
// 		}
// 		im = remain
// 	}

// 	return condFalse
// }

// // checkPreconditions evaluates request preconditions and reports whether a precondition
// // resulted in sending StatusNotModified or StatusPreconditionFailed.
// func checkPreconditions(w http.ResponseWriter, r *http.Request, modtime time.Time) (done bool, rangeHeader string) {
// 	// This function carefully follows RFC 7232 section 6.
// 	ch := checkIfMatch(w, r)
// 	if ch == condNone {
// 		ch = checkIfUnmodifiedSince(r, modtime)
// 	}
// 	if ch == condFalse {
// 		w.WriteHeader(StatusPreconditionFailed)
// 		return true, ""
// 	}
// 	switch checkIfNoneMatch(w, r) {
// 	case condFalse:
// 		if r.Method == "GET" || r.Method == "HEAD" {
// 			writeNotModified(w)
// 			return true, ""
// 		} else {
// 			w.WriteHeader(StatusPreconditionFailed)
// 			return true, ""
// 		}
// 	case condNone:
// 		if checkIfModifiedSince(r, modtime) == condFalse {
// 			writeNotModified(w)
// 			return true, ""
// 		}
// 	}

// 	rangeHeader = r.Header.get("Range")
// 	if rangeHeader != "" && checkIfRange(w, r, modtime) == condFalse {
// 		rangeHeader = ""
// 	}
// 	return false, rangeHeader
// }
// func ServeContent(w http.ResponseWriter, req *http.Request, name string, modtime time.Time, content io.ReadSeeker) {
// 	sizeFunc := func() (int64, error) {
// 		size, err := content.Seek(0, io.SeekEnd)
// 		if err != nil {
// 			return 0, errSeeker
// 		}
// 		_, err = content.Seek(0, io.SeekStart)
// 		if err != nil {
// 			return 0, errSeeker
// 		}
// 		return size, nil
// 	}
// 	serveContent(w, req, name, modtime, sizeFunc, content)
// }

// func serveContent(w http.ResponseWriter, r *http.Request, name string, modtime time.Time, sizeFunc func() (int64, error), content io.ReadSeeker) {
// 	setLastModified(w, modtime)
// 	done, rangeReq := checkPreconditions(w, r, modtime)
// 	if done {
// 		return
// 	}

// 	code := StatusOK

// 	// If Content-Type isn't set, use the file's extension to find it, but
// 	// if the Content-Type is unset explicitly, do not sniff the type.
// 	ctypes, haveType := w.Header()["Content-Type"]
// 	var ctype string
// 	if !haveType {
// 		ctype = mime.TypeByExtension(filepath.Ext(name))
// 		if ctype == "" {
// 			// read a chunk to decide between utf-8 text and binary
// 			var buf [sniffLen]byte
// 			n, _ := io.ReadFull(content, buf[:])
// 			ctype = DetectContentType(buf[:n])
// 			_, err := content.Seek(0, io.SeekStart) // rewind to output whole file
// 			if err != nil {
// 				Error(w, "seeker can't seek", StatusInternalServerError)
// 				return
// 			}
// 		}
// 		w.Header().Set("Content-Type", ctype)
// 	} else if len(ctypes) > 0 {
// 		ctype = ctypes[0]
// 	}

// 	size, err := sizeFunc()
// 	if err != nil {
// 		Error(w, err.Error(), StatusInternalServerError)
// 		return
// 	}

// 	// handle Content-Range header.
// 	sendSize := size
// 	var sendContent io.Reader = content
// 	if size >= 0 {
// 		ranges, err := parseRange(rangeReq, size)
// 		if err != nil {
// 			if err == errNoOverlap {
// 				w.Header().Set("Content-Range", fmt.Sprintf("bytes */%d", size))
// 			}
// 			Error(w, err.Error(), StatusRequestedRangeNotSatisfiable)
// 			return
// 		}
// 		if sumRangesSize(ranges) > size {
// 			// The total number of bytes in all the ranges
// 			// is larger than the size of the file by
// 			// itself, so this is probably an attack, or a
// 			// dumb client. Ignore the range request.
// 			ranges = nil
// 		}
// 		switch {
// 		case len(ranges) == 1:
// 			// RFC 2616, Section 14.16:
// 			// "When an HTTP message includes the content of a single
// 			// range (for example, a response to a request for a
// 			// single range, or to a request for a set of ranges
// 			// that overlap without any holes), this content is
// 			// transmitted with a Content-Range header, and a
// 			// Content-Length header showing the number of bytes
// 			// actually transferred.
// 			// ...
// 			// A response to a request for a single range MUST NOT
// 			// be sent using the multipart/byteranges media type."
// 			ra := ranges[0]
// 			if _, err := content.Seek(ra.start, io.SeekStart); err != nil {
// 				Error(w, err.Error(), StatusRequestedRangeNotSatisfiable)
// 				return
// 			}
// 			sendSize = ra.length
// 			code = StatusPartialContent
// 			w.Header().Set("Content-Range", ra.contentRange(size))
// 		case len(ranges) > 1:
// 			sendSize = rangesMIMESize(ranges, ctype, size)
// 			code = StatusPartialContent

// 			pr, pw := io.Pipe()
// 			mw := multipart.NewWriter(pw)
// 			w.Header().Set("Content-Type", "multipart/byteranges; boundary="+mw.Boundary())
// 			sendContent = pr
// 			defer pr.Close() // cause writing goroutine to fail and exit if CopyN doesn't finish.
// 			go func() {
// 				for _, ra := range ranges {
// 					part, err := mw.CreatePart(ra.mimeHeader(ctype, size))
// 					if err != nil {
// 						pw.CloseWithError(err)
// 						return
// 					}
// 					if _, err := content.Seek(ra.start, io.SeekStart); err != nil {
// 						pw.CloseWithError(err)
// 						return
// 					}
// 					if _, err := io.CopyN(part, content, ra.length); err != nil {
// 						pw.CloseWithError(err)
// 						return
// 					}
// 				}
// 				mw.Close()
// 				pw.Close()
// 			}()
// 		}

// 		w.Header().Set("Accept-Ranges", "bytes")
// 		if w.Header().Get("Content-Encoding") == "" {
// 			w.Header().Set("Content-Length", strconv.FormatInt(sendSize, 10))
// 		}
// 	}

// 	w.WriteHeader(code)

// 	if r.Method != "HEAD" {
// 		io.CopyN(w, sendContent, sendSize)
// 	}
// }

// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// HTTP file system request handler
//=================================================================================
// import (
// 	"errors"
// 	"fmt"
// 	"io"
// 	"log"
// 	"mime"
// 	"mime/multipart"
// 	"net/http"
// 	"net/textproto"
// 	"net/url"
// 	"os"
// 	"path"
// 	"path/filepath"
// 	"sort"
// 	"strconv"
// 	"strings"
// 	"time"
// )

// // A Dir implements FileSystem using the native file system restricted to a
// // specific directory tree.
// //
// // While the FileSystem.Open method takes '/'-separated paths, a Dir's string
// // value is a filename on the native file system, not a URL, so it is separated
// // by filepath.Separator, which isn't necessarily '/'.
// //
// // Note that Dir will allow access to files and directories starting with a
// // period, which could expose sensitive directories like a .git directory or
// // sensitive files like .htpasswd. To exclude files with a leading period,
// // remove the files/directories from the server or create a custom FileSystem
// // implementation.
// //
// // An empty Dir is treated as ".".
// type Dir string

// // mapDirOpenError maps the provided non-nil error from opening name
// // to a possibly better non-nil error. In particular, it turns OS-specific errors
// // about opening files in non-directories into os.ErrNotExist. See Issue 18984.
// func mapDirOpenError(originalErr error, name string) error {
// 	if os.IsNotExist(originalErr) || os.IsPermission(originalErr) {
// 		return originalErr
// 	}

// 	parts := strings.Split(name, string(filepath.Separator))
// 	for i := range parts {
// 		if parts[i] == "" {
// 			continue
// 		}
// 		fi, err := os.Stat(strings.Join(parts[:i+1], string(filepath.Separator)))
// 		if err != nil {
// 			return originalErr
// 		}
// 		if !fi.IsDir() {
// 			return os.ErrNotExist
// 		}
// 	}
// 	return originalErr
// }

// func (d Dir) Open(name string) (File, error) {
// 	if filepath.Separator != '/' && strings.ContainsRune(name, filepath.Separator) {
// 		return nil, errors.New("http: invalid character in file path")
// 	}
// 	dir := string(d)
// 	if dir == "" {
// 		dir = "."
// 	}
// 	fullName := filepath.Join(dir, filepath.FromSlash(path.Clean("/"+name)))
// 	f, err := os.Open(fullName)
// 	if err != nil {
// 		return nil, mapDirOpenError(err, fullName)
// 	}
// 	return f, nil
// }

// // A FileSystem implements access to a collection of named files.
// // The elements in a file path are separated by slash ('/', U+002F)
// // characters, regardless of host operating system convention.
// type FileSystem interface {
// 	Open(name string) (File, error)
// }

// // A File is returned by a FileSystem's Open method and can be
// // served by the FileServer implementation.
// //
// // The methods should behave the same as those on an *os.File.
// type File interface {
// 	io.Closer
// 	io.Reader
// 	io.Seeker
// 	Readdir(count int) ([]os.FileInfo, error)
// 	Stat() (os.FileInfo, error)
// }

// func Error(w http.ResponseWriter, error string, code int) {
// 	w.Header().Set("Content-Type", "text/plain; charset=utf-8")
// 	w.Header().Set("X-Content-Type-Options", "nosniff")
// 	w.WriteHeader(code)
// 	fmt.Fprintln(w, error)
// }

// func dirList(w http.ResponseWriter, r *http.Request, f File) {
// 	dirs, err := f.Readdir(-1)
// 	if err != nil {
// 		logf(r, "http: error reading directory: %v", err)
// 		Error(w, "Error reading directory", http.StatusInternalServerError)
// 		return
// 	}
// 	sort.Slice(dirs, func(i, j int) bool { return dirs[i].Name() < dirs[j].Name() })

// 	w.Header().Set("Content-Type", "text/html; charset=utf-8")
// 	fmt.Fprintf(w, "<pre>\n")
// 	for _, d := range dirs {
// 		name := d.Name()
// 		if d.IsDir() {
// 			name += "/"
// 		}
// 		// name may contain '?' or '#', which must be escaped to remain
// 		// part of the URL path, and not indicate the start of a query
// 		// string or fragment.
// 		url := url.URL{Path: name}
// 		fmt.Fprintf(w, "<a href=\"%s\">%s</a>\n", url.String(), htmlReplacer.Replace(name))
// 	}
// 	fmt.Fprintf(w, "</pre>\n")
// }

// // logf prints to the ErrorLog of the *Server associated with request r
// // via ServerContextKey. If there's no associated server, or if ErrorLog
// // is nil, logging is done via the log package's standard logger.
// func logf(r *http.Request, format string, args ...interface{}) {
// 	s, _ := r.Context().Value(ServerContextKey).(*Server)
// 	if s != nil && s.ErrorLog != nil {
// 		s.ErrorLog.Printf(format, args...)
// 	} else {
// 		log.Printf(format, args...)
// 	}
// }

// // ServeContent replies to the request using the content in the
// // provided ReadSeeker. The main benefit of ServeContent over io.Copy
// // is that it handles Range requests properly, sets the MIME type, and
// // handles If-Match, If-Unmodified-Since, If-None-Match, If-Modified-Since,
// // and If-Range requests.
// //
// // If the response's Content-Type header is not set, ServeContent
// // first tries to deduce the type from name's file extension and,
// // if that fails, falls back to reading the first block of the content
// // and passing it to DetectContentType.
// // The name is otherwise unused; in particular it can be empty and is
// // never sent in the response.
// //
// // If modtime is not the zero time or Unix epoch, ServeContent
// // includes it in a Last-Modified header in the response. If the
// // request includes an If-Modified-Since header, ServeContent uses
// // modtime to decide whether the content needs to be sent at all.
// //
// // The content's Seek method must work: ServeContent uses
// // a seek to the end of the content to determine its size.
// //
// // If the caller has set w's ETag header formatted per RFC 7232, section 2.3,
// // ServeContent uses it to handle requests using If-Match, If-None-Match, or If-Range.
// //
// // Note that *os.File implements the io.ReadSeeker interface.
// func ServeContent(w http.ResponseWriter, req *http.Request, name string, modtime time.Time, content io.ReadSeeker) {
// 	sizeFunc := func() (int64, error) {
// 		size, err := content.Seek(0, io.SeekEnd)
// 		if err != nil {
// 			return 0, errSeeker
// 		}
// 		_, err = content.Seek(0, io.SeekStart)
// 		if err != nil {
// 			return 0, errSeeker
// 		}
// 		return size, nil
// 	}
// 	serveContent(w, req, name, modtime, sizeFunc, content)
// }

// // errSeeker is returned by ServeContent's sizeFunc when the content
// // doesn't seek properly. The underlying Seeker's error text isn't
// // included in the sizeFunc reply so it's not sent over HTTP to end
// // users.
// var errSeeker = errors.New("seeker can't seek")

// // errNoOverlap is returned by serveContent's parseRange if first-byte-pos of
// // all of the byte-range-spec values is greater than the content size.
// var errNoOverlap = errors.New("invalid range: failed to overlap")

// // The algorithm uses at most sniffLen bytes to make its decision.
// const sniffLen = 512

// // if name is empty, filename is unknown. (used for mime type, before sniffing)
// // if modtime.IsZero(), modtime is unknown.
// // content must be seeked to the beginning of the file.
// // The sizeFunc is called at most once. Its error, if any, is sent in the HTTP response.
// func serveContent(w http.ResponseWriter, r *http.Request, name string, modtime time.Time, sizeFunc func() (int64, error), content io.ReadSeeker) {
// 	setLastModified(w, modtime)
// 	done, rangeReq := checkPreconditions(w, r, modtime)
// 	if done {
// 		return
// 	}

// 	code := StatusOK

// 	// If Content-Type isn't set, use the file's extension to find it, but
// 	// if the Content-Type is unset explicitly, do not sniff the type.
// 	ctypes, haveType := w.Header()["Content-Type"]
// 	var ctype string
// 	if !haveType {
// 		ctype = mime.TypeByExtension(filepath.Ext(name))
// 		if ctype == "" {
// 			// read a chunk to decide between utf-8 text and binary
// 			var buf [sniffLen]byte
// 			n, _ := io.ReadFull(content, buf[:])
// 			ctype = http.DetectContentType(buf[:n])
// 			_, err := content.Seek(0, io.SeekStart) // rewind to output whole file
// 			if err != nil {
// 				Error(w, "seeker can't seek", http.StatusInternalServerError)
// 				return
// 			}
// 		}
// 		w.Header().Set("Content-Type", ctype)
// 	} else if len(ctypes) > 0 {
// 		ctype = ctypes[0]
// 	}

// 	size, err := sizeFunc()
// 	if err != nil {
// 		Error(w, err.Error(), StatusInternalServerError)
// 		return
// 	}

// 	// handle Content-Range header.
// 	sendSize := size
// 	var sendContent io.Reader = content
// 	if size >= 0 {
// 		ranges, err := parseRange(rangeReq, size)
// 		if err != nil {
// 			if err == errNoOverlap {
// 				w.Header().Set("Content-Range", fmt.Sprintf("bytes */%d", size))
// 			}
// 			Error(w, err.Error(), StatusRequestedRangeNotSatisfiable)
// 			return
// 		}
// 		if sumRangesSize(ranges) > size {
// 			// The total number of bytes in all the ranges
// 			// is larger than the size of the file by
// 			// itself, so this is probably an attack, or a
// 			// dumb client. Ignore the range request.
// 			ranges = nil
// 		}
// 		switch {
// 		case len(ranges) == 1:
// 			// RFC 2616, Section 14.16:
// 			// "When an HTTP message includes the content of a single
// 			// range (for example, a response to a request for a
// 			// single range, or to a request for a set of ranges
// 			// that overlap without any holes), this content is
// 			// transmitted with a Content-Range header, and a
// 			// Content-Length header showing the number of bytes
// 			// actually transferred.
// 			// ...
// 			// A response to a request for a single range MUST NOT
// 			// be sent using the multipart/byteranges media type."
// 			ra := ranges[0]
// 			if _, err := content.Seek(ra.start, io.SeekStart); err != nil {
// 				Error(w, err.Error(), StatusRequestedRangeNotSatisfiable)
// 				return
// 			}
// 			sendSize = ra.length
// 			code = StatusPartialContent
// 			w.Header().Set("Content-Range", ra.contentRange(size))
// 		case len(ranges) > 1:
// 			sendSize = rangesMIMESize(ranges, ctype, size)
// 			code = StatusPartialContent

// 			pr, pw := io.Pipe()
// 			mw := multipart.NewWriter(pw)
// 			w.Header().Set("Content-Type", "multipart/byteranges; boundary="+mw.Boundary())
// 			sendContent = pr
// 			defer pr.Close() // cause writing goroutine to fail and exit if CopyN doesn't finish.
// 			go func() {
// 				for _, ra := range ranges {
// 					part, err := mw.CreatePart(ra.mimeHeader(ctype, size))
// 					if err != nil {
// 						pw.CloseWithError(err)
// 						return
// 					}
// 					if _, err := content.Seek(ra.start, io.SeekStart); err != nil {
// 						pw.CloseWithError(err)
// 						return
// 					}
// 					if _, err := io.CopyN(part, content, ra.length); err != nil {
// 						pw.CloseWithError(err)
// 						return
// 					}
// 				}
// 				mw.Close()
// 				pw.Close()
// 			}()
// 		}

// 		w.Header().Set("Accept-Ranges", "bytes")
// 		if w.Header().Get("Content-Encoding") == "" {
// 			w.Header().Set("Content-Length", strconv.FormatInt(sendSize, 10))
// 		}
// 	}

// 	w.WriteHeader(code)

// 	if r.Method != "HEAD" {
// 		io.CopyN(w, sendContent, sendSize)
// 	}
// }

// // scanETag determines if a syntactically valid ETag is present at s. If so,
// // the ETag and remaining text after consuming ETag is returned. Otherwise,
// // it returns "", "".
// func scanETag(s string) (etag string, remain string) {
// 	s = textproto.TrimString(s)
// 	start := 0
// 	if strings.HasPrefix(s, "W/") {
// 		start = 2
// 	}
// 	if len(s[start:]) < 2 || s[start] != '"' {
// 		return "", ""
// 	}
// 	// ETag is either W/"text" or "text".
// 	// See RFC 7232 2.3.
// 	for i := start + 1; i < len(s); i++ {
// 		c := s[i]
// 		switch {
// 		// Character values allowed in ETags.
// 		case c == 0x21 || c >= 0x23 && c <= 0x7E || c >= 0x80:
// 		case c == '"':
// 			return s[:i+1], s[i+1:]
// 		default:
// 			return "", ""
// 		}
// 	}
// 	return "", ""
// }

// // etagStrongMatch reports whether a and b match using strong ETag comparison.
// // Assumes a and b are valid ETags.
// func etagStrongMatch(a, b string) bool {
// 	return a == b && a != "" && a[0] == '"'
// }

// // etagWeakMatch reports whether a and b match using weak ETag comparison.
// // Assumes a and b are valid ETags.
// func etagWeakMatch(a, b string) bool {
// 	return strings.TrimPrefix(a, "W/") == strings.TrimPrefix(b, "W/")
// }

// // condResult is the result of an HTTP request precondition check.
// // See https://tools.ietf.org/html/rfc7232 section 3.
// type condResult int

// const (
// 	condNone condResult = iota
// 	condTrue
// 	condFalse
// )

// func checkIfMatch(w http.ResponseWriter, r *http.Request) condResult {
// 	im := r.Header.Get("If-Match")
// 	if im == "" {
// 		return condNone
// 	}
// 	for {
// 		im = textproto.TrimString(im)
// 		if len(im) == 0 {
// 			break
// 		}
// 		if im[0] == ',' {
// 			im = im[1:]
// 			continue
// 		}
// 		if im[0] == '*' {
// 			return condTrue
// 		}
// 		etag, remain := scanETag(im)
// 		if etag == "" {
// 			break
// 		}
// 		if etagStrongMatch(etag, w.Header().get("Etag")) {
// 			return condTrue
// 		}
// 		im = remain
// 	}

// 	return condFalse
// }

// func checkIfUnmodifiedSince(r *http.Request, modtime time.Time) condResult {
// 	ius := r.Header.Get("If-Unmodified-Since")
// 	if ius == "" || isZeroTime(modtime) {
// 		return condNone
// 	}
// 	if t, err := ParseTime(ius); err == nil {
// 		// The Date-Modified header truncates sub-second precision, so
// 		// use mtime < t+1s instead of mtime <= t to check for unmodified.
// 		if modtime.Before(t.Add(1 * time.Second)) {
// 			return condTrue
// 		}
// 		return condFalse
// 	}
// 	return condNone
// }

// func checkIfNoneMatch(w http.ResponseWriter, r *http.Request) condResult {
// 	inm := r.Header.get("If-None-Match")
// 	if inm == "" {
// 		return condNone
// 	}
// 	buf := inm
// 	for {
// 		buf = textproto.TrimString(buf)
// 		if len(buf) == 0 {
// 			break
// 		}
// 		if buf[0] == ',' {
// 			buf = buf[1:]
// 		}
// 		if buf[0] == '*' {
// 			return condFalse
// 		}
// 		etag, remain := scanETag(buf)
// 		if etag == "" {
// 			break
// 		}
// 		if etagWeakMatch(etag, w.Header().get("Etag")) {
// 			return condFalse
// 		}
// 		buf = remain
// 	}
// 	return condTrue
// }

// func checkIfModifiedSince(r *http.Request, modtime time.Time) condResult {
// 	if r.Method != "GET" && r.Method != "HEAD" {
// 		return condNone
// 	}
// 	ims := r.Header.Get("If-Modified-Since")
// 	if ims == "" || isZeroTime(modtime) {
// 		return condNone
// 	}
// 	t, err := ParseTime(ims)
// 	if err != nil {
// 		return condNone
// 	}
// 	// The Date-Modified header truncates sub-second precision, so
// 	// use mtime < t+1s instead of mtime <= t to check for unmodified.
// 	if modtime.Before(t.Add(1 * time.Second)) {
// 		return condFalse
// 	}
// 	return condTrue
// }

// func checkIfRange(w http.ResponseWriter, r *http.Request, modtime time.Time) condResult {
// 	if r.Method != "GET" && r.Method != "HEAD" {
// 		return condNone
// 	}
// 	ir := r.Header.get("If-Range")
// 	if ir == "" {
// 		return condNone
// 	}
// 	etag, _ := scanETag(ir)
// 	if etag != "" {
// 		if etagStrongMatch(etag, w.Header().Get("Etag")) {
// 			return condTrue
// 		} else {
// 			return condFalse
// 		}
// 	}
// 	// The If-Range value is typically the ETag value, but it may also be
// 	// the modtime date. See golang.org/issue/8367.
// 	if modtime.IsZero() {
// 		return condFalse
// 	}
// 	t, err := ParseTime(ir)
// 	if err != nil {
// 		return condFalse
// 	}
// 	if t.Unix() == modtime.Unix() {
// 		return condTrue
// 	}
// 	return condFalse
// }

// var unixEpochTime = time.Unix(0, 0)

// // isZeroTime reports whether t is obviously unspecified (either zero or Unix()=0).
// func isZeroTime(t time.Time) bool {
// 	return t.IsZero() || t.Equal(unixEpochTime)
// }

// func setLastModified(w http.ResponseWriter, modtime time.Time) {
// 	if !isZeroTime(modtime) {
// 		w.Header().Set("Last-Modified", modtime.UTC().Format(TimeFormat))
// 	}
// }

// func writeNotModified(w http.ResponseWriter) {
// 	// RFC 7232 section 4.1:
// 	// a sender SHOULD NOT generate representation metadata other than the
// 	// above listed fields unless said metadata exists for the purpose of
// 	// guiding cache updates (e.g., Last-Modified might be useful if the
// 	// response does not have an ETag field).
// 	h := w.Header()
// 	delete(h, "Content-Type")
// 	delete(h, "Content-Length")
// 	if h.Get("Etag") != "" {
// 		delete(h, "Last-Modified")
// 	}
// 	w.WriteHeader(StatusNotModified)
// }

// // checkPreconditions evaluates request preconditions and reports whether a precondition
// // resulted in sending StatusNotModified or StatusPreconditionFailed.
// func checkPreconditions(w http.ResponseWriter, r *http.Request, modtime time.Time) (done bool, rangeHeader string) {
// 	// This function carefully follows RFC 7232 section 6.
// 	ch := checkIfMatch(w, r)
// 	if ch == condNone {
// 		ch = checkIfUnmodifiedSince(r, modtime)
// 	}
// 	if ch == condFalse {
// 		w.WriteHeader(StatusPreconditionFailed)
// 		return true, ""
// 	}
// 	switch checkIfNoneMatch(w, r) {
// 	case condFalse:
// 		if r.Method == "GET" || r.Method == "HEAD" {
// 			writeNotModified(w)
// 			return true, ""
// 		} else {
// 			w.WriteHeader(StatusPreconditionFailed)
// 			return true, ""
// 		}
// 	case condNone:
// 		if checkIfModifiedSince(r, modtime) == condFalse {
// 			writeNotModified(w)
// 			return true, ""
// 		}
// 	}

// 	rangeHeader = r.Header.get("Range")
// 	if rangeHeader != "" && checkIfRange(w, r, modtime) == condFalse {
// 		rangeHeader = ""
// 	}
// 	return false, rangeHeader
// }

// // name is '/'-separated, not filepath.Separator.
// func serveFile(w http.ResponseWriter, r *http.Request, fs FileSystem, name string, redirect bool) {
// 	const indexPage = "/index.html"

// 	// redirect .../index.html to .../
// 	// can't use Redirect() because that would make the path absolute,
// 	// which would be a problem running under StripPrefix
// 	if strings.HasSuffix(r.URL.Path, indexPage) {
// 		localRedirect(w, r, "./")
// 		return
// 	}

// 	f, err := fs.Open(name)
// 	if err != nil {
// 		msg, code := toHTTPError(err)
// 		Error(w, msg, code)
// 		return
// 	}
// 	defer f.Close()

// 	d, err := f.Stat()
// 	if err != nil {
// 		msg, code := toHTTPError(err)
// 		Error(w, msg, code)
// 		return
// 	}

// 	if redirect {
// 		// redirect to canonical path: / at end of directory url
// 		// r.URL.Path always begins with /
// 		url := r.URL.Path
// 		if d.IsDir() {
// 			if url[len(url)-1] != '/' {
// 				localRedirect(w, r, path.Base(url)+"/")
// 				return
// 			}
// 		} else {
// 			if url[len(url)-1] == '/' {
// 				localRedirect(w, r, "../"+path.Base(url))
// 				return
// 			}
// 		}
// 	}

// 	// redirect if the directory name doesn't end in a slash
// 	if d.IsDir() {
// 		url := r.URL.Path
// 		if url[len(url)-1] != '/' {
// 			localRedirect(w, r, path.Base(url)+"/")
// 			return
// 		}
// 	}

// 	// use contents of index.html for directory, if present
// 	if d.IsDir() {
// 		index := strings.TrimSuffix(name, "/") + indexPage
// 		ff, err := fs.Open(index)
// 		if err == nil {
// 			defer ff.Close()
// 			dd, err := ff.Stat()
// 			if err == nil {
// 				name = index
// 				d = dd
// 				f = ff
// 			}
// 		}
// 	}

// 	// Still a directory? (we didn't find an index.html file)
// 	if d.IsDir() {
// 		if checkIfModifiedSince(r, d.ModTime()) == condFalse {
// 			writeNotModified(w)
// 			return
// 		}
// 		w.Header().Set("Last-Modified", d.ModTime().UTC().Format(TimeFormat))
// 		dirList(w, r, f)
// 		return
// 	}

// 	// serveContent will check modification time
// 	sizeFunc := func() (int64, error) { return d.Size(), nil }
// 	serveContent(w, r, d.Name(), d.ModTime(), sizeFunc, f)
// }

// // toHTTPError returns a non-specific HTTP error message and status code
// // for a given non-nil error value. It's important that toHTTPError does not
// // actually return err.Error(), since msg and httpStatus are returned to users,
// // and historically Go's ServeContent always returned just "404 Not Found" for
// // all errors. We don't want to start leaking information in error messages.
// func toHTTPError(err error) (msg string, httpStatus int) {
// 	if os.IsNotExist(err) {
// 		return "404 page not found", StatusNotFound
// 	}
// 	if os.IsPermission(err) {
// 		return "403 Forbidden", StatusForbidden
// 	}
// 	// Default:
// 	return "500 Internal Server Error", StatusInternalServerError
// }

// // localRedirect gives a Moved Permanently response.
// // It does not convert relative paths to absolute paths like Redirect does.
// func localRedirect(w http.ResponseWriter, r *http.Request, newPath string) {
// 	if q := r.URL.RawQuery; q != "" {
// 		newPath += "?" + q
// 	}
// 	w.Header().Set("Location", newPath)
// 	w.WriteHeader(StatusMovedPermanently)
// }

// // ServeFile replies to the request with the contents of the named
// // file or directory.
// //
// // If the provided file or directory name is a relative path, it is
// // interpreted relative to the current directory and may ascend to parent
// // directories. If the provided name is constructed from user input, it
// // should be sanitized before calling ServeFile. As a precaution, ServeFile
// // will reject requests where r.URL.Path contains a ".." path element.
// //
// // As a special case, ServeFile redirects any request where r.URL.Path
// // ends in "/index.html" to the same path, without the final
// // "index.html". To avoid such redirects either modify the path or
// // use ServeContent.
// func ServeFile(w http.ResponseWriter, r *http.Request, name string) {
// 	if containsDotDot(r.URL.Path) {
// 		// Too many programs use r.URL.Path to construct the argument to
// 		// serveFile. Reject the request under the assumption that happened
// 		// here and ".." may not be wanted.
// 		// Note that name might not contain "..", for example if code (still
// 		// incorrectly) used filepath.Join(myDir, r.URL.Path).
// 		Error(w, "invalid URL path", StatusBadRequest)
// 		return
// 	}
// 	dir, file := filepath.Split(name)
// 	serveFile(w, r, Dir(dir), file, false)
// }

// func containsDotDot(v string) bool {
// 	if !strings.Contains(v, "..") {
// 		return false
// 	}
// 	for _, ent := range strings.FieldsFunc(v, isSlashRune) {
// 		if ent == ".." {
// 			return true
// 		}
// 	}
// 	return false
// }

// func isSlashRune(r rune) bool { return r == '/' || r == '\\' }

// type fileHandler struct {
// 	root FileSystem
// }

// // FileServer returns a handler that serves HTTP requests
// // with the contents of the file system rooted at root.
// //
// // To use the operating system's file system implementation,
// // use http.Dir:
// //
// //     http.Handle("/", http.FileServer(http.Dir("/tmp")))
// //
// // As a special case, the returned file server redirects any request
// // ending in "/index.html" to the same path, without the final
// // "index.html".
// func FileServer(root FileSystem) Handler {
// 	return &fileHandler{root}
// }

// func (f *fileHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
// 	upath := r.URL.Path
// 	if !strings.HasPrefix(upath, "/") {
// 		upath = "/" + upath
// 		r.URL.Path = upath
// 	}
// 	serveFile(w, r, f.root, path.Clean(upath), true)
// }

// // httpRange specifies the byte range to be sent to the client.
// type httpRange struct {
// 	start, length int64
// }

// func (r httpRange) contentRange(size int64) string {
// 	return fmt.Sprintf("bytes %d-%d/%d", r.start, r.start+r.length-1, size)
// }

// func (r httpRange) mimeHeader(contentType string, size int64) textproto.MIMEHeader {
// 	return textproto.MIMEHeader{
// 		"Content-Range": {r.contentRange(size)},
// 		"Content-Type":  {contentType},
// 	}
// }

// // parseRange parses a Range header string as per RFC 2616.
// // errNoOverlap is returned if none of the ranges overlap.
// func parseRange(s string, size int64) ([]httpRange, error) {
// 	if s == "" {
// 		return nil, nil // header not present
// 	}
// 	const b = "bytes="
// 	if !strings.HasPrefix(s, b) {
// 		return nil, errors.New("invalid range")
// 	}
// 	var ranges []httpRange
// 	noOverlap := false
// 	for _, ra := range strings.Split(s[len(b):], ",") {
// 		ra = strings.TrimSpace(ra)
// 		if ra == "" {
// 			continue
// 		}
// 		i := strings.Index(ra, "-")
// 		if i < 0 {
// 			return nil, errors.New("invalid range")
// 		}
// 		start, end := strings.TrimSpace(ra[:i]), strings.TrimSpace(ra[i+1:])
// 		var r httpRange
// 		if start == "" {
// 			// If no start is specified, end specifies the
// 			// range start relative to the end of the file.
// 			i, err := strconv.ParseInt(end, 10, 64)
// 			if err != nil {
// 				return nil, errors.New("invalid range")
// 			}
// 			if i > size {
// 				i = size
// 			}
// 			r.start = size - i
// 			r.length = size - r.start
// 		} else {
// 			i, err := strconv.ParseInt(start, 10, 64)
// 			if err != nil || i < 0 {
// 				return nil, errors.New("invalid range")
// 			}
// 			if i >= size {
// 				// If the range begins after the size of the content,
// 				// then it does not overlap.
// 				noOverlap = true
// 				continue
// 			}
// 			r.start = i
// 			if end == "" {
// 				// If no end is specified, range extends to end of the file.
// 				r.length = size - r.start
// 			} else {
// 				i, err := strconv.ParseInt(end, 10, 64)
// 				if err != nil || r.start > i {
// 					return nil, errors.New("invalid range")
// 				}
// 				if i >= size {
// 					i = size - 1
// 				}
// 				r.length = i - r.start + 1
// 			}
// 		}
// 		ranges = append(ranges, r)
// 	}
// 	if noOverlap && len(ranges) == 0 {
// 		// The specified ranges did not overlap with the content.
// 		return nil, errNoOverlap
// 	}
// 	return ranges, nil
// }

// // countingWriter counts how many bytes have been written to it.
// type countingWriter int64

// func (w *countingWriter) Write(p []byte) (n int, err error) {
// 	*w += countingWriter(len(p))
// 	return len(p), nil
// }

// // rangesMIMESize returns the number of bytes it takes to encode the
// // provided ranges as a multipart response.
// func rangesMIMESize(ranges []httpRange, contentType string, contentSize int64) (encSize int64) {
// 	var w countingWriter
// 	mw := multipart.NewWriter(&w)
// 	for _, ra := range ranges {
// 		mw.CreatePart(ra.mimeHeader(contentType, contentSize))
// 		encSize += ra.length
// 	}
// 	mw.Close()
// 	encSize += int64(w)
// 	return
// }

// func sumRangesSize(ranges []httpRange) (size int64) {
// 	for _, ra := range ranges {
// 		size += ra.length
// 	}
// 	return
// }
